﻿#region Using Statements
using GameBase.Screens;
using Microsoft.Xna.Framework;
using StarFox.ScreenComponents;
#if DEBUG
using StarFox.Testing;
#endif
using System;
using System.Collections.Generic;
using StarFox.Inputs;
using StarFox.Sounds;
using StarFox.Graphics;
using System.Diagnostics;

#endregion

namespace StarFox.Screens
{
    /// <summary>
    /// Gameplay screen of the game.
    /// </summary>
    public partial class GamePlayScreen : StarFoxScreen
    {
        #region Fields

        StarFoxVibration vibration;
        GamePlaySound sound;
        PostScreenProcessor postScreenProcessor;
        GameScore gameScore;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of this class.
        /// </summary>
        /// <param name="screenManager">The ScreenManager that this Screen is associated to.</param>
        public GamePlayScreen(ScreenManager screenManager)
            : base(screenManager)
        {
            Name = "GamePlayScreen";

            vibration = Game.Services.GetService(typeof(StarFoxVibration)) as StarFoxVibration;
            if (vibration == null)
                throw new InvalidOperationException("Service of type StarFoxVibration not found");

            sound = Game.Services.GetService(typeof(GamePlaySound)) as GamePlaySound;
            if (sound == null)
                throw new InvalidOperationException("Service of type StarFoxSound not found");

            postScreenProcessor = Game.Services.GetService(typeof(PostScreenProcessor)) as PostScreenProcessor;
            if (postScreenProcessor == null)
                throw new InvalidOperationException("Service of type PostScreenProcessor not found");

            gameScore = Game.Services.GetService(typeof(GameScore)) as GameScore;
            if (gameScore == null)
                throw new InvalidOperationException("Service of type GameScores not found");
        }

        #endregion

        #region Initialize

        /// <summary>
        /// Called when the Screen needs to be initialized.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            Initialize_StarShip();
            Initialize_SkyBox();
            Initialize_AsteroidField();
            Initialize_LensFlare();
            Initialize_ScreenMessages();
            Initialize_HeadUpDisplay();

#if DEBUG
            Components2D.Add(new TestInfo(this));
#endif
        }

        #region StarShip

        // sample StarShip's velocity only once every few frames
        int velCnt = 0;

        StarShip ship;

        private void Initialize_StarShip()
        {
            ship = new StarShip(this)
            {
                BombModeDuration = TimeSpan.FromMilliseconds(5000),
                HealthOutFuelDuration = TimeSpan.FromMilliseconds(20000)
            };

            ship.BombModeActivated += (sender, args) =>
            {
                hud.BombMode();
                message.BombMode();
            };

            ship.OutBoost += (sender, args) =>
            {
                warning.OutBoost();
            };

            Components3D.Add(ship);
        }

        #endregion

        #region SkyBox

        private void Initialize_SkyBox()
        {
            Components3D.Add(new SkyBox(this) { AspectRatio = AspectRatio });
            Components3D.Add(new ColorSmoke2D(this));
        }

        #endregion

        #region AsteroidField

        // play whosh sound effect only once every few frames
        int whoshCnt = 0;

        AsteroidField asteroidField;

        private void Initialize_AsteroidField()
        {
            asteroidField = new AsteroidField(this, ship, 5000);
            //asteroidField = new AsteroidField(this, ship, 500);

            asteroidField.Collision += (sender, args) =>
            {
                // collision with power-up
                if (args.PowerUpType.HasValue)
                {
                    ShowPowerUp(args.PowerUpType.Value);
                }
                // collision with asteroid
                else if (args.CollisionType.HasValue)
                {
                    if (ship.IsBombMode)
                    {
                        gameScore.Score -= 50;
                        message.Collision(true);
                        ship.Collision(null);
                        vibration.Start(PlayerIndex, 0.25f, 0.25f, TimeSpan.FromMilliseconds(1000));
                        return;
                    }

                    switch (args.CollisionType.Value)
                    {
                        case AsteroidField.CollisionType.LEFT:
                            {
                                hud.Collision(AsteroidField.CollisionType.LEFT);
                                ship.Collision(AsteroidField.CollisionType.LEFT);
                                vibration.Start(PlayerIndex, 0.5f, 0.5f, TimeSpan.FromMilliseconds(1000));
                                break;
                            }
                        case AsteroidField.CollisionType.CENTER:
                            {
                                hud.Collision(AsteroidField.CollisionType.CENTER);
                                ship.Collision(AsteroidField.CollisionType.CENTER);
                                vibration.Start(PlayerIndex, 1.0f, 1.0f, TimeSpan.FromMilliseconds(1000));
                                break;
                            }
                        case AsteroidField.CollisionType.RIGHT:
                            {
                                hud.Collision(AsteroidField.CollisionType.RIGHT);
                                ship.Collision(AsteroidField.CollisionType.RIGHT);
                                vibration.Start(PlayerIndex, 0.5f, 0.5f, TimeSpan.FromMilliseconds(1000));
                                break;
                            }
                    }

                    gameScore.Score -= 100;
                    if (ship.Health != 0.0f) { message.Collision(false); }
                }
            };

            asteroidField.PassBy += (sender, args) =>
            {
                // % 12 => five times per second
                if (whoshCnt % 12 == 0)
                {
                    vibration.Start(PlayerIndex, 0.3f, 0.3f, TimeSpan.FromMilliseconds(500));

                    foreach (Vector3 position in args.Positions)
                    {
                        gameScore.Score += 5;
                        sound.Whosh(position, ship.Position);
                    }

                    whoshCnt = 0;
                }
            };

            Components3D.Add(asteroidField);
            Components3D.Add(new ColorSmoke3D(this));
        }

        private void ShowPowerUp(PowerUp.PowerUpType type)
        {
            switch (type)
            {
                case PowerUp.PowerUpType.LIFE:
                    {
                        gameScore.Lives++;
                        gameScore.Score += 300;
                        break;
                    }
                case PowerUp.PowerUpType.BOMB:
                    {
                        gameScore.Score += 200;
                        break;
                    }
                default:
                    {
                        gameScore.Score += 100;
                        break;
                    }
            }

            hud.PowerUp(type);
            message.PowerUp(type);
            ship.PowerUp(type);
        }

        #endregion

        #region LensFlare

        LensFlare lensFlare;

        private void Initialize_LensFlare()
        {
            List<LensFlare.Flare> flareList = new List<LensFlare.Flare>
            {
                new LensFlare.Flare(-0.45f, 0.85f, new Color(100, 200, 100, 20), "RING"),
                new LensFlare.Flare(-0.25f, 0.65f, new Color(175, 175, 255, 20), "CIRCLE"),
                new LensFlare.Flare(-0.10f, 0.30f, new Color(200, 50, 50, 150), "GLOW"),

                new LensFlare.Flare(0.15f, 0.90f, new Color(255, 255, 255, 255), "SUN"),
                new LensFlare.Flare(0.15f, 0.90f, new Color(255, 255, 255, 128), "STREAKS"),
                new LensFlare.Flare(0.15f, 2.25f, new Color(255, 255, 200, 150), "GLOW"),
                
                new LensFlare.Flare(0.45f, 0.55f, new Color(50, 50, 255, 60), "RING"),
                new LensFlare.Flare(0.50f, 0.20f, new Color(50, 50, 255, 150), "GLOW"),
                new LensFlare.Flare(0.55f, 0.35f, new Color(50, 50, 255, 60), "CIRCLE"),
                new LensFlare.Flare(0.65f, 0.25f, new Color(50, 50, 255, 60), "CIRCLE"),

                new LensFlare.Flare(0.77f, 0.35f, new Color(255, 40, 40, 50), "RING"),
                new LensFlare.Flare(0.80f, 0.20f, new Color(255, 0, 0, 150), "GLOW"),
                new LensFlare.Flare(0.82f, 0.25f, new Color(255, 40, 40, 30), "CIRCLE"),
                                
                new LensFlare.Flare(1.15f, 0.90f, new Color(50, 200, 50, 255), "GLOW"),
                new LensFlare.Flare(0.95f, 0.50f, new Color(50, 200, 200, 150), "GLOW"),
                new LensFlare.Flare(1.40f, 1.00f, new Color(100, 50, 50, 30), "RING"),
            };
            lensFlare = new LensFlare(this, flareList, 200) { LightColor = new Color(170, 170, 170), };
            Components3D.Add(lensFlare);
        }

        readonly TimeSpan LENS_FLARE_TIME = TimeSpan.FromSeconds(100);
        float lensFlareAngle = StarFoxHelper.RandomFloat(0.0f, MathHelper.TwoPi);
        bool lensFlare_CW;

        private void Update_LensFlare(GameTime gameTime)
        {
            float timeDelta = (float)(gameTime.ElapsedGameTime.TotalMilliseconds / LENS_FLARE_TIME.TotalMilliseconds);

            if (lensFlare_CW)
            {
                lensFlareAngle -= MathHelper.TwoPi * timeDelta;
                lensFlareAngle = MathHelper.Clamp(lensFlareAngle, 0.0f, MathHelper.TwoPi);
                if (lensFlareAngle == 0.0f) { lensFlare_CW = !lensFlare_CW; }
            }
            else
            {
                lensFlareAngle += MathHelper.TwoPi * timeDelta;
                lensFlareAngle = MathHelper.Clamp(lensFlareAngle, 0.0f, MathHelper.TwoPi);
                if (lensFlareAngle == MathHelper.TwoPi) { lensFlare_CW = !lensFlare_CW; }
            }

            double radius = Math.Sqrt(Math.Pow(0.75 * Math.Cos(lensFlareAngle), 2) + Math.Pow(0.5 * Math.Sin(lensFlareAngle), 2));
            lensFlare.LightDirection = new Vector3((float)(-radius * Math.Cos(lensFlareAngle)), (float)(-radius * (float)Math.Sin(lensFlareAngle)), 1.0f);
        }

        #endregion

        #region HeadUpDisplay

        HeadUpDisplay hud;

        private void Initialize_HeadUpDisplay()
        {
            hud = new HeadUpDisplay(this)
            {
                WarningEffectDuration = TimeSpan.FromMilliseconds(1000),
                BombModeDuration = TimeSpan.FromMilliseconds(5000),
            };
            Components2D.Add(hud);
        }

        private void Update_HeadUpDisplay()
        {
            hud.Lives = gameScore.Lives;
            hud.Score = gameScore.Score;
            hud.LevelPosition = gameScore.LevelPosition + asteroidField.LevelPosition;
            hud.PlayTime = gameScore.PlayTime;
            hud.Speed = ship.Velocity;

            hud.Bombs = ship.Bombs;
            hud.Fuel = ship.Fuel;
            hud.Health = ship.Health;
            hud.BoostLevel = ship.Boost;
        }

        #endregion

        #region ScreenMessages

        CountDownMessage countDown;
        GamePlayMessage message;
        GameOverMessage gameOver;
        VictoryMessage victory;
        WarningMessage warning;
        PauseMessage pause;

        private void Initialize_ScreenMessages()
        {
            countDown = new CountDownMessage(this)
            {
                Duration = TimeSpan.FromMilliseconds(6000)
            };
            countDown.Finishing += (sender, args) =>
            {
                state = GAMESTATE.GAMEPLAY;
                ship.Start();
            };
            Components2D.Add(countDown);

            message = new GamePlayMessage(this)
            {
                Duration = TimeSpan.FromMilliseconds(1000)
            };
            Components2D.Add(message);

            gameOver = new GameOverMessage(this)
            {
                Duration = TimeSpan.FromMilliseconds(8000)
            };
            gameOver.Finishing += (sender, args) =>
            {
                ScreenManager.AddScreen(new GameOverScreen(ScreenManager));
            };
            Components2D.Add(gameOver);

            victory = new VictoryMessage(this)
            {
                Duration = TimeSpan.FromMilliseconds(4000)
            };
            victory.Finishing += (sender, args) =>
            {
                ScreenManager.AddScreen(new VictoryScreen(ScreenManager));
            };
            Components2D.Add(victory);

            warning = new WarningMessage(this)
            {
                Duration = TimeSpan.FromMilliseconds(500)
            };
            Components2D.Add(warning);

            pause = new PauseMessage(this);
            Components2D.Add(pause);
        }

        #endregion

        #endregion

        #region Dispose

        // dispose flag
        bool isDisposed = false;

        /// <summary>
        /// Releases the unmanaged resources used by this object and optionally releases the managed resources.
        /// </summary>
        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if (!isDisposed)
            {
                if (disposing)
                {
                    vibration.Cancel();

                    base.Dispose(disposing);
                }

                isDisposed = true;
            }
        }

        #endregion
    }
}
